from collections import namedtuple
from enum import Enum
import shutil
import os
import subprocess
import time
import pdb

import langs
import sys

Result = namedtuple(
    "Result", "score message status time_cost memory_cost ")
Testcase = namedtuple("Testcase", "input output")
Output = namedtuple("Output", "answer user_output input")
CompileResult = namedtuple("CompileResult", "time stdout stderr args")


class JudgeType(Enum):
    # 标准模式，逐行比较，忽略行首行尾空格
    standard = 1
    # 使用spj进行比较
    special_judge = 2


class JudgeException(Exception):
    result: Result
    output: Output

    def __init__(self, result, output=None):
        self.result = result
        self.output = output


class CompileException(Exception):
    message: str

    def get_message(self):
        return self.message

    def __init__(self, message=None):
        self.message = message
# 运行一个评测任务，返回结果

# exec_name:可执行文件名
# input_file 输入文件名
# output_file 输出文件名
# work_dir 工作目录
# time_limit 时间限制，ms
# memory_limit 内存限制 MB
# config_name 语言配置文件
# using_docker 是否在docker内评测
# 要求评测机必须是linux系统，而且安装了docker，而且docker中有ubuntu镜像


def run_standard(exec_name: str,
        input_file: str,
        output_file: str,
        work_dir: str,
        time_limit: int,
        memory_limit: int,
        config_name: str="cpp",
        args: str="O2",
        using_docker=False
        ):
    def to_absolute(path):
        return os.path.join(work_dir, path)
    input = open(to_absolute(input_file), "r")
    try:
        input_text = input.readlines()
        try:
            begin = time.time()
            __import__("langs."+config_name)
            app = subprocess.run(sys.modules["langs."+config_name].get_exec_cmdline(args).format(exec_name=to_absolute(exec_name)), input="".join(input_text),
                                 timeout=time_limit/1000, universal_newlines=True, stdout=subprocess.PIPE)
            end = time.time()
        except subprocess.TimeoutExpired as ex:
            raise JudgeException(
                Result(0, "Timeout limit exceeded", "TLE", (time.time()-begin)*1000, -1))
        input.close()
        if app.returncode != 0:
            raise JudgeException(
                Result(0, "Runtime Error", "RE", (end-begin)*1000, -1))
        with open(to_absolute(output_file), "r") as standard:
            answer = standard.readlines()
        output = app.stdout.split("\n")
        flag, message = check(work_dir, output, answer, JudgeType.standard)
        if not flag:
            raise JudgeException(
                Result(0, message, "WA", (end-begin)*1000, -1), (input_text, output, answer))
    except JudgeException as ex:
        return (ex.result, ex.output)
    except Exception as ex:
        # pdb.set_trace()
        raise ex
        return ex
    return (Result(100, "Accepted", "AC", (end-begin)*1000, -1), None)


def check(work_dir: str, output: list, answer: list, judge_type: JudgeType, args: str=None):
    if judge_type != JudgeType.special_judge:
        while len(answer) > 0 and (answer[-1] is None or answer[-1].strip() == ""):
            answer.pop()
        while len(output) > 0 and (output[-1] is None or output[-1].strip() == ""):
            output.pop()
        if len(output) != len(answer):
            return False, "answer lines:{} your output:{}".format(len(answer), len(output))
    if judge_type == JudgeType.standard:
        for i in range(0, len(output)):
            if output[i].strip() != answer[i].strip():
                return False, "wrong at line {}.".format(i)
        return True, "Accepted"
    else:
        pass


def compile_exec(work_dir, source, output, config_name="cpp", args="O2", timeout=5000)->CompileResult:
    try:
        begin = time.time()
        __import__("langs."+config_name)
        for compile_cmd in sys.modules["langs."+config_name].get_compile_cmdlines(args):
            compile_cmd = compile_cmd.format(source=os.path.join(
                work_dir, source), output=os.path.join(work_dir, output))
            app = subprocess.run(compile_cmd, timeout=timeout /
                                 1000,  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        end = time.time()
    except subprocess.TimeoutExpired as ex:
        raise CompileException(
            "Compile timed out:{}s".format(time.time()-begin))
    if app.returncode != 0:
        raise CompileException(
            "Compile error:\nstdout:\n{}\nstderr:\n{}\ncommand line:\n{}".format(app.stdout.decode("utf-8"), app.stderr.decode("utf-8"), app.args))
    return CompileResult((end-begin)*1000, app.stdout.decode("utf-8"), app.stderr.decode("utf-8"), app.args)
